module;

#include <cstddef>
#include <cstdint> //https://zh.cppreference.com/w/cpp/types/integer
#include <algorithm>

#include <print>

#include <bit>

export module cpu6502:instruct;

import :hardware;

export namespace nes8
{
    // https://www.nesdev.org/obelisk-6502-guide/instructions.html
    // https://www.masswerk.at/6502/6502_instruction_set.html

    enum Instruction6502
    {
        // Load/Store Operations
        LDA, // Load Accumulator 	N,Z
        LDX, // Load X Register 	N,Z
        LDY, // Load Y Register 	N,Z
        STA, // Store Accumulator
        STX, // Store X Register
        STY, // Store Y Register

        // Register Transfers
        TAX, // Transfer accumulator to X 	N,Z
        TAY, // Transfer accumulator to Y 	N,Z
        TXA, // Transfer X to accumulator 	N,Z
        TYA, // Transfer Y to accumulator 	N,Z

        // Stack Operations
        TSX, // Transfer stack pointer to X 	N,Z
        TXS, // Transfer X to stack pointer
        PHA, // Push accumulator on stack
        PHP, // Push processor status on stack
        PLA, // Pull accumulator from stack 	N,Z
        PLP, // Pull processor status from stack 	All

        // Logical
        AND, // Logical AND 	N,Z
        EOR, // Exclusive OR 	N,Z
        ORA, // Logical Inclusive OR 	N,Z
        BIT, // Bit Test 	N,V,Z

        // Arithmetic
        ADC, // Add with Carry 	N,V,Z,C
        SBC, // Subtract with Carry 	N,V,Z,C
        CMP, // Compare accumulator 	N,Z,C
        CPX, // Compare X register 	N,Z,C
        CPY, // Compare Y register 	N,Z,C

        // Increments & Decrements
        INC, // Increment a memory location 	N,Z
        INX, // Increment the X register 	N,Z
        INY, // Increment the Y register 	N,Z
        DEC, // Decrement a memory location 	N,Z
        DEX, // Decrement the X register 	N,Z
        DEY, // Decrement the Y register 	N,Z
        ASL, // Arithmetic Shift Left 	N,Z,C

        // Shifts
        LSR, // Logical Shift Right 	N,Z,C
        ROL, // Rotate Left 	N,Z,C
        ROR, // Rotate Right 	N,Z,C
        JMP, // Jump to another location

        // Jumps & Calls
        JSR, // Jump to a subroutine
        RTS, // Return from subroutine
        BCC, // Branch if carry flag clear

        // Branches
        BCS, // Branch if carry flag set
        BEQ, // Branch if zero flag set
        BMI, // Branch if negative flag set
        BNE, // Branch if zero flag clear
        BPL, // Branch if negative flag clear
        BVC, // Branch if overflow flag clear
        BVS, // Branch if overflow flag set

        // Status Flag Changes
        CLC, // Clear carry flag 	C
        CLD, // Clear decimal mode flag 	D
        CLI, // Clear interrupt disable flag 	I
        CLV, // Clear overflow flag 	V
        SEC, // Set carry flag 	C
        SED, // Set decimal mode flag 	D
        SEI, // Set interrupt disable flag 	I

        // System Functions
        BRK, // Force an interrupt 	B
        NOP, // No Operation
        RTI, // Return from Interrupt 	All
    };

    enum register_p_flag
    {
        Carry = 0,
        Zero,
        I,
        D,
        Break,
        UNUSED,
        OverFlow,
        Negative
    };

    template <register_p_flag flag>
    void set_flag(byte6502 &p_register)
    {
        p_register |= (0b1 << flag);
    }
    template <register_p_flag flag>
    void clear_flag(byte6502 &p_register)
    {
        p_register &= ~(0b1 << flag);
    }

    template <std::endian ed = std::endian::native>
    uint16_t make_uint16(uint8_t low, uint8_t high);

    template <>
    uint16_t make_uint16<std::endian::little>(uint8_t low, uint8_t high)
    {
        return (low << 8) | high;
    }

    template <>
    uint16_t make_uint16<std::endian::big>(uint8_t low, uint8_t high)
    {
        return (high << 8) | low;
    }

    ptr_6502 read_as_ptr_6502(memory &mem, ptr_6502 address)
    {
        return make_uint16<std::endian::big>(mem[address], mem[address + 1]);
    }

    template <Instruction6502 instruction>
    void make_reg_flag(cpu_register &reg);

    template <>
    void make_reg_flag<LDA>(cpu_register &reg)
    {
        if (reg.a & 0b10000000)
            set_flag<Negative>(reg.p);
        if (reg.a == 0)
            set_flag<Zero>(reg.p);
    }

    ////// Address Modes ///////////
    // https://www.nesdev.org/obelisk-6502-guide/addressing.html

    enum AddressMode
    {
        Implicit,    // 不需要实现
        Accumulator, // 不需要实现
        Immediate,
        ZeroPage,
        ZeroPageX,
        ZeroPageY,
        Relative,
        Absolute,
        AbsoluteX,
        AbsoluteY,
        Indirect,
        IndirectX,
        IndirectY
    };

    template<Instruction6502 instruction, AddressMode mode>
    struct instruction_info;

    template<>
    struct instruction_info<LDA, Immediate>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xA9;

        static constexpr auto cycle_ = 2;
    };

    template<>
    struct instruction_info<LDA,ZeroPage>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xA5;
        static constexpr auto cycle_ = 3;
    };

    template<>
    struct instruction_info<LDA,ZeroPageX>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xB5;
        static constexpr auto cycle_ = 4;
    };

    template<>
    struct instruction_info<LDA,Absolute>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xAD;
        static constexpr auto cycle_ = 4;
    };
    
    template<>
    struct instruction_info<LDA,AbsoluteX>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xBD;
        static constexpr auto cycle_ = 4;
    };

    template <>
    struct instruction_info<LDA, AbsoluteY>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xB9;
        static constexpr auto cycle_ = 4;
    };

    template <>
    struct instruction_info<LDA, IndirectX>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xA1;
        static constexpr auto cycle_ = 6;
    };

    template <>
    struct instruction_info<LDA, IndirectY>
    {
        static constexpr auto name_ = "LDA";
        static constexpr auto opcode_ = 0xB1;
        static constexpr auto cycle_ = 5;
    };

    template <AddressMode mode>
    ptr_6502 make_addr(cpu_register &reg, memory &mem);

    template <>
    ptr_6502 make_addr<Immediate>(cpu_register &reg, memory &mem)
    {
        auto operand = mem[reg.pc + 1];
        // 总是8位地址
        return operand;
    }

    // 下面三个返回的地址都在ZeroPage范围内
    template <>
    ptr_6502 make_addr<ZeroPage>(cpu_register &reg, memory &mem)
    {
        return make_addr<Immediate>(reg, mem);
    }

    template <>
    ptr_6502 make_addr<ZeroPageX>(cpu_register &reg, memory &mem)
    {
        return make_addr<ZeroPage>(reg, mem) + reg.x;
    }

    template <>
    ptr_6502 make_addr<ZeroPageY>(cpu_register &reg, memory &mem)
    {
        return make_addr<ZeroPage>(reg, mem) + reg.y;
    }

    template <>
    ptr_6502 make_addr<Relative>(cpu_register &reg, memory &mem)
    {
        auto operand = mem[reg.pc + 1];
        // BNE *+4 指令 D0 02
        // 实际上4中包含了当前指令的长度,所以最终要把当前指令大小减去
        //  因为我实现的是指令结束增加pc,不知道有没有影响
        return (reg.pc + 2 + operand);
    }

    template <>
    ptr_6502 make_addr<Absolute>(cpu_register &reg, memory &mem)
    {   
        auto operand = read_as_ptr_6502(mem, mem[reg.pc + 1]);
        return operand;
    }

    template <>
    ptr_6502 make_addr<AbsoluteX>(cpu_register &reg, memory &mem)
    {
        return make_addr<Absolute>(reg, mem) + reg.x;
    }
    
    template <>
    ptr_6502 make_addr<AbsoluteY>(cpu_register &reg, memory &mem)
    {
        return make_addr<Absolute>(reg, mem) + reg.y;
    }

    template <>
    ptr_6502 make_addr<Indirect>(cpu_register &reg, memory &mem)
    {
        return read_as_ptr_6502(mem, make_addr<Absolute>(reg, mem));
    }

    template <>
    ptr_6502 make_addr<IndirectX>(cpu_register &reg, memory &mem)
    {
        auto address = make_addr<ZeroPageX>(reg, mem);
        return read_as_ptr_6502(mem, address);
    }

    template <>
    ptr_6502 make_addr<IndirectY>(cpu_register &reg, memory &mem)
    {
        auto address = make_addr<ZeroPage>(reg, mem);
        return read_as_ptr_6502(mem, address) + reg.y;
    }

    template<AddressMode mode>
    cycle_type cross_page_cycle(ptr_6502 address)
    {
           return 0;
    }

    template <>
    cycle_type cross_page_cycle<AbsoluteX>(ptr_6502 address)
    {
        [[unlikely]]if(address & 0x0100)
                return 1;
        return 0;
    }

    template <>
    cycle_type cross_page_cycle<AbsoluteY>(ptr_6502 address)
    {
        return cross_page_cycle<AbsoluteX>(address);
    }

    template <>
    cycle_type cross_page_cycle<IndirectY>(ptr_6502 address)
    {
        return cross_page_cycle<AbsoluteX>(address);
    }

    /////////////// INSTRUCTIONS //////////////

    template <Instruction6502 instruction, AddressMode mode>
    std::size_t execute_instruction(cpu_register &reg, memory &mem);

    //$A9
    template <>
    std::size_t execute_instruction<LDA, Immediate>(cpu_register &reg, memory &mem)
    {
        reg.a = make_addr<Immediate>(reg, mem);
        make_reg_flag<LDA>(reg);
        
        reg.pc += 2;
        return 2;
    }
    //$A5
    template <>
    std::size_t execute_instruction<LDA, ZeroPage>(cpu_register &reg, memory &mem)
    {
        reg.a = mem[make_addr<ZeroPage>(reg, mem)];
        make_reg_flag<LDA>(reg);

        reg.pc += 2;
        return 3;
    }

    //$B5
    template <>
    std::size_t execute_instruction<LDA, ZeroPageX>(cpu_register &reg, memory &mem)
    {
        reg.a = mem[make_addr<ZeroPage>(reg, mem)];
        make_reg_flag<LDA>(reg);

        reg.pc += 2;
        return 4;
    }

    //$AD
    template <>
    std::size_t execute_instruction<LDA, Absolute>(cpu_register &reg, memory &mem)
    {
        reg.a = mem[make_addr<Absolute>(reg, mem)];
        make_reg_flag<LDA>(reg);

        reg.pc += 3;
        return 4;
    }

    //$BD
    template <>
    std::size_t execute_instruction<LDA, AbsoluteX>(cpu_register &reg, memory &mem)
    {
        reg.a = mem[make_addr<AbsoluteX>(reg, mem)];
        make_reg_flag<LDA>(reg);

        reg.pc += 3;
        [[unlikely]] if (reg.pc & 0x0100)
            return 5; // 跨页
        return 4;
    }

    //$B9
    template <>
    std::size_t execute_instruction<LDA, AbsoluteY>(cpu_register &reg, memory &mem)
    {
        reg.a = mem[make_addr<AbsoluteY>(reg, mem)];
        make_reg_flag<LDA>(reg);
        
        reg.pc += 3;
        [[unlikely]] if (reg.pc & 0x0100)
            return 5; // 跨页
        return 4;
    }

    // $A1
    template <>
    std::size_t execute_instruction<LDA, IndirectX>(cpu_register &reg, memory &mem)
    {
        reg.a = mem[make_addr<IndirectX>(reg, mem)];
        make_reg_flag<LDA>(reg);

        reg.pc += 2;
        return 6;
    }

    // $B1
    template <>
    std::size_t execute_instruction<LDA, IndirectY>(cpu_register &reg, memory &mem)
    {
        reg.a = mem[make_addr<IndirectY>(reg, mem)];
        make_reg_flag<LDA>(reg);

        reg.pc += 2;
        [[unlikely]] if (reg.pc & 0x0100)
            return 6; // 跨页
        return 5;
    }

} // namespace nes8
